<!DOCTYPE html>
<html>
	<head>
		<title>URL地址校验</title>
		<meta charset="utf-8">
		<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
	</head>
	<body>
		<h1>URL地址校验</h1>
		<form>
			<label for="file-input">请选择文件：</label>
			<input type="file" id="file-input">
			<br>
			<button type="button" id="check-button">校验可用链接</button>
			<button type="button" id="open-button">一键打开可用链接</button>
		</form>
		<br>
		<table id="result-table">
			<thead>
				<tr>
					<th>序号</th>
					<th>链接地址</th>
					<th>是否可用</th>
				</tr>
			</thead>
			<tbody>
			</tbody>
		</table>
		<script>
			// 页面元素获取和事件绑定函数
			function bindEvents() {
				$("#check-button").click(handleCheckButtonClick);
				$("#open-button").click(handleOpenButtonClick);
			}

			// 对每个链接进行校验
			async function handleCheckButtonClick() {
				// 清空之前的校验结果
				$("#result-table tbody").empty();

				// 获取文件内容并进行长度校验
				const file = $("#file-input")[0].files[0];
				if (!file) {
					alert("请选择文件！");
					return;
				}
				const maxLength = 1000000; // 最大文件大小为 1 MB
				if (file.size > maxLength) {
					alert("文件大小超过最大输入上限！");
					return;
				}

				// 读取文件内容并识别所有的链接
				const reader = new FileReader();
				reader.readAsText(file);
				reader.onerror = () => alert('文件读取失败，请重新选择！');
				reader.onload = async () => {
					const text = reader.result;
					const urls = text.match(
						/((http|https):\/\/)[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/ig
					);

					// 没有检测到链接
					if (!urls || urls.length === 0) {
						alert('文件中没有检测到链接，请重新选择文件！');
						return;
					}

					// 显示 loading 效果
					$("body").addClass("loading");

					const result = await checkLinks(urls);
					// 将链接按是否可用分组
					const groups = _.groupBy(result, 'isAvailable');

					groups.true && groups.true.forEach((link, index) => {
						$('#result-table tbody').append(`
			      <tr>
			        <td>${index+1}</td>
			        <td><a href="${link.url}" target="_blank">${link.url}</a></td>
			        <td>可用（${link.latency}毫秒）</td>
			      </tr>
			    `);
					});

					// 输出不可用的链接
					groups.false && groups.false.forEach((link, index) => {
						$('#result-table tbody').append(`
			      <tr>
			        <td>${(groups.true ? groups.true.length : 0) + index + 1}</td>
			        <td>${link.url}</td>
			        <td>不可用</td>
			      </tr>
			    `);
					});

					// 隐藏 loading 效果
					$("body").removeClass("loading");
				};
			}

			// 将打开链接函数绑定到打开链接按钮上
			function handleOpenButtonClick() {
				const urls = $("#result-table tbody tr td:nth-child(2) a").toArray().map(link => link.href);
				openMultipleUrls(urls);
			}

			// 发送请求，检查链接是否可用
			async function checkLink(url) {
				const startTime = new Date().getTime();
				try {
					await $.ajax({
						url,
						type: "HEAD",
						timeout: 5000 // 超过5秒则认为链接不可用
					});
					const endTime = new Date().getTime();
					const latency = endTime - startTime;
					return {
						url,
						latency,
						isAvailable: true
					};
				} catch (e) {
					return {
						url,
						isAvailable: false
					};
				}
			}
			
			// 发送请求，检查链接是否可用（校验时间比较长）
			// async function checkLink(url) {
			// 	const startTime = new Date().getTime();
			// 	try {
			// 		await $.ajax({
			// 			url,
			// 			type: "HEAD",
			// 			timeout: 5000 // 超过5秒则认为链接不可用
			// 		});
			// 		const endTime = new Date().getTime();
			// 		const latency = endTime - startTime;
			// 		return {
			// 			url,
			// 			latency,
			// 			isAvailable: true
			// 		};
			// 	} catch (e) {
			// 		// 如果是HEAD请求失败，则尝试使用GET请求
			// 		try {
			// 			const res = await fetch(url, {
			// 				method: 'GET'
			// 			});
			// 			if (res.status === 200) {
			// 				const endTime = new Date().getTime();
			// 				const latency = endTime - startTime;
			// 				return {
			// 					url,
			// 					latency,
			// 					isAvailable: true
			// 				};
			// 			}
			// 		} catch (e) {}
			
			// 		return {
			// 			url,
			// 			isAvailable: false
			// 		};
			// 	}
			// }

			// 使用 Promise 对象优化异步操作
			async function checkLinks(urls) {
				const promiseList = urls.map(url => checkLink(url));
				return Promise.all(promiseList);
			}

			// 打开多个链接并间隔一段时间
			function openMultipleUrls(urls) {
				for (let i = 0; i < urls.length; i++) {
					setTimeout(function() {
						try {
							window.open(urls[i]);
						} catch (e) {
							console.error("Error opening URL: " + urls[i]);
						}
					}, i * 1000);
				}
			}

			// 页面加载完毕后执行的函数
			$(document).ready(function() {
				bindEvents();
			});
		</script>
		<style>
			.loading::after {
				content: " ";
				display: block;
				position: fixed;
				top: 50%;
				left: 50%;
				margin-top: -50px;
				margin-left: -50px;
				width: 100px;
				height: 100px;
				border-radius: 50%;
				border: 10px solid #f3f3f3;
				border-top: 10px solid #3498db;
				animation: spin 2s linear infinite;
			}

			@keyframes spin {
				0% {
					transform: rotate(0deg);
				}

				100% {
					transform: rotate(360deg);
				}
			}

			/* 给 form 元素下方添加间距 */
			form {
				margin-bottom: 20px;
			}

			/* 给表格加上外边框和间距 */
			table {
				border-collapse: collapse;
				margin-top: 20px;
				margin-bottom: 20px;
			}

			td,
			th {
				padding: 10px;
				border: 1px solid #ccc;
			}
		</style>
	</body>
</html>